-module(cus_pb).

-export([encode/1, encode/2, decode/2, encode_kick/1,
	 decode_kick/1, encode_pushcontent/1,
	 decode_pushcontent/1, encode_messageack/1,
	 decode_messageack/1, encode_msg/1, decode_msg/1,
	 encode_conversationmessage/1,
	 decode_conversationmessage/1, encode_conversationinfo/1,
	 decode_conversationinfo/1, encode_userinfo/1,
	 decode_userinfo/1, encode_loginresp/1,
	 decode_loginresp/1, encode_login/1, decode_login/1,
	 encode_error/1, decode_error/1, encode_bound/1,
	 decode_bound/1, encode_handshakeresp/1,
	 decode_handshakeresp/1, encode_handshake/1,
	 decode_handshake/1]).

-record(kick, {time}).

-record(pushcontent, {alert, extra}).

-record(messageack,
	{mid, conversationinfo, sid, msgtime, extra}).

-record(msg,
	{mid, conversationid, conversationinfo, msgtype,
	 fromuser, members, content, msgtime, msgstatus}).

-record(conversationmessage, {totalnumber, msg}).

-record(conversationinfo,
	{cid, name, members, status, updatetime}).

-record(userinfo,
	{uid, mutenotofication, pushcontent, blacks, follows,
	 emotions, latelyemotions, mutenotofications, sticks}).

-record(loginresp, {userinfo, conversationmessage}).

-record(login, {server, uid, token, platform, pushkey}).

-record(error, {code, errormsg}).

-record(bound, {type, size}).

-record(handshakeresp, {code}).

-record(handshake, {secretkey}).

encode(Record) ->
    encode(erlang:element(1, Record), Record).

encode_kick(Record) when is_record(Record, kick) ->
    encode(kick, Record).

encode_pushcontent(Record)
    when is_record(Record, pushcontent) ->
    encode(pushcontent, Record).

encode_messageack(Record)
    when is_record(Record, messageack) ->
    encode(messageack, Record).

encode_msg(Record) when is_record(Record, msg) ->
    encode(msg, Record).

encode_conversationmessage(Record)
    when is_record(Record, conversationmessage) ->
    encode(conversationmessage, Record).

encode_conversationinfo(Record)
    when is_record(Record, conversationinfo) ->
    encode(conversationinfo, Record).

encode_userinfo(Record)
    when is_record(Record, userinfo) ->
    encode(userinfo, Record).

encode_loginresp(Record)
    when is_record(Record, loginresp) ->
    encode(loginresp, Record).

encode_login(Record) when is_record(Record, login) ->
    encode(login, Record).

encode_error(Record) when is_record(Record, error) ->
    encode(error, Record).

encode_bound(Record) when is_record(Record, bound) ->
    encode(bound, Record).

encode_handshakeresp(Record)
    when is_record(Record, handshakeresp) ->
    encode(handshakeresp, Record).

encode_handshake(Record)
    when is_record(Record, handshake) ->
    encode(handshake, Record).

encode(handshake, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#handshake.secretkey, none),
			   string, [])]);
encode(handshakeresp, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#handshakeresp.code, none),
			   sfixed32, [])]);
encode(bound, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#bound.type, none), sfixed32,
			   []),
		      pack(2, required,
			   with_default(_Record#bound.size, none), sfixed32,
			   [])]);
encode(error, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#error.code, none), string, []),
		      pack(2, required,
			   with_default(_Record#error.errormsg, none), string,
			   [])]);
encode(login, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#login.server, none), string,
			   []),
		      pack(2, required, with_default(_Record#login.uid, none),
			   string, []),
		      pack(3, required,
			   with_default(_Record#login.token, none), string, []),
		      pack(4, required,
			   with_default(_Record#login.platform, none), string,
			   []),
		      pack(5, required,
			   with_default(_Record#login.pushkey, none), string,
			   [])]);
encode(loginresp, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#loginresp.userinfo, none),
			   userinfo, []),
		      pack(2, repeated,
			   with_default(_Record#loginresp.conversationmessage,
					none),
			   conversationmessage, [])]);
encode(userinfo, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#userinfo.uid, none), string,
			   []),
		      pack(2, required,
			   with_default(_Record#userinfo.mutenotofication,
					none),
			   int32, []),
		      pack(3, required,
			   with_default(_Record#userinfo.pushcontent, none),
			   int32, []),
		      pack(4, required,
			   with_default(_Record#userinfo.blacks, none), string,
			   []),
		      pack(5, repeated,
			   with_default(_Record#userinfo.follows, none),
			   conversationinfo, []),
		      pack(6, required,
			   with_default(_Record#userinfo.emotions, none),
			   string, []),
		      pack(7, required,
			   with_default(_Record#userinfo.latelyemotions, none),
			   string, []),
		      pack(8, repeated,
			   with_default(_Record#userinfo.mutenotofications,
					none),
			   conversationinfo, []),
		      pack(9, repeated,
			   with_default(_Record#userinfo.sticks, none),
			   conversationinfo, [])]);
encode(conversationinfo, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#conversationinfo.cid, none),
			   string, []),
		      pack(2, required,
			   with_default(_Record#conversationinfo.name, none),
			   string, []),
		      pack(3, required,
			   with_default(_Record#conversationinfo.members, none),
			   string, []),
		      pack(4, required,
			   with_default(_Record#conversationinfo.status, none),
			   int32, []),
		      pack(5, required,
			   with_default(_Record#conversationinfo.updatetime,
					none),
			   string, [])]);
encode(conversationmessage, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#conversationmessage.totalnumber,
					none),
			   int32, []),
		      pack(2, repeated,
			   with_default(_Record#conversationmessage.msg, none),
			   msg, [])]);
encode(msg, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#msg.mid, none), string, []),
		      pack(2, required,
			   with_default(_Record#msg.conversationid, none),
			   string, []),
		      pack(3, optional,
			   with_default(_Record#msg.conversationinfo, none),
			   conversationinfo, []),
		      pack(4, required,
			   with_default(_Record#msg.msgtype, none), string, []),
		      pack(5, required,
			   with_default(_Record#msg.fromuser, none), string,
			   []),
		      pack(6, required,
			   with_default(_Record#msg.members, none), string, []),
		      pack(7, required,
			   with_default(_Record#msg.content, none), string, []),
		      pack(8, required,
			   with_default(_Record#msg.msgtime, none), string, []),
		      pack(9, required,
			   with_default(_Record#msg.msgstatus, none), string,
			   [])]);
encode(messageack, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#messageack.mid, none), string,
			   []),
		      pack(2, optional,
			   with_default(_Record#messageack.conversationinfo,
					none),
			   conversationinfo, []),
		      pack(3, required,
			   with_default(_Record#messageack.sid, none), string,
			   []),
		      pack(4, required,
			   with_default(_Record#messageack.msgtime, none),
			   string, []),
		      pack(5, required,
			   with_default(_Record#messageack.extra, none), string,
			   [])]);
encode(pushcontent, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#pushcontent.alert, none),
			   string, []),
		      pack(2, required,
			   with_default(_Record#pushcontent.extra, none),
			   string, [])]);
encode(kick, _Record) ->
    iolist_to_binary([pack(1, required,
			   with_default(_Record#kick.time, none), string, [])]).

with_default(undefined, none) -> undefined;
with_default(undefined, Default) -> Default;
with_default(Val, _) -> Val.

pack(_, optional, undefined, _, _) -> [];
pack(_, repeated, undefined, _, _) -> [];
pack(FNum, required, undefined, Type, _) ->
    exit({error,
	  {required_field_is_undefined, FNum, Type}});
pack(_, repeated, [], _, Acc) -> lists:reverse(Acc);
pack(FNum, repeated, [Head | Tail], Type, Acc) ->
    pack(FNum, repeated, Tail, Type,
	 [pack(FNum, optional, Head, Type, []) | Acc]);
pack(FNum, _, Data, _, _) when is_tuple(Data) ->
    RecName = erlang:element(1, Data),
    protobuffs:encode(FNum, encode(RecName, Data), bytes);
pack(FNum, _, Data, Type, _) ->
    protobuffs:encode(FNum, Data, Type).

decode_kick(Bytes) -> decode(kick, Bytes).

decode_pushcontent(Bytes) -> decode(pushcontent, Bytes).

decode_messageack(Bytes) -> decode(messageack, Bytes).

decode_msg(Bytes) -> decode(msg, Bytes).

decode_conversationmessage(Bytes) ->
    decode(conversationmessage, Bytes).

decode_conversationinfo(Bytes) ->
    decode(conversationinfo, Bytes).

decode_userinfo(Bytes) -> decode(userinfo, Bytes).

decode_loginresp(Bytes) -> decode(loginresp, Bytes).

decode_login(Bytes) -> decode(login, Bytes).

decode_error(Bytes) -> decode(error, Bytes).

decode_bound(Bytes) -> decode(bound, Bytes).

decode_handshakeresp(Bytes) ->
    decode(handshakeresp, Bytes).

decode_handshake(Bytes) -> decode(handshake, Bytes).

decode(handshake, Bytes) ->
    Types = [{1, secretkey, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(handshake, Decoded);
decode(handshakeresp, Bytes) ->
    Types = [{1, code, sfixed32, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(handshakeresp, Decoded);
decode(bound, Bytes) ->
    Types = [{2, size, sfixed32, []},
	     {1, type, sfixed32, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(bound, Decoded);
decode(error, Bytes) ->
    Types = [{2, errormsg, string, []},
	     {1, code, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(error, Decoded);
decode(login, Bytes) ->
    Types = [{5, pushkey, string, []},
	     {4, platform, string, []}, {3, token, string, []},
	     {2, uid, string, []}, {1, server, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(login, Decoded);
decode(loginresp, Bytes) ->
    Types = [{2, conversationmessage, conversationmessage,
	      [is_record, repeated]},
	     {1, userinfo, userinfo, [is_record]}],
    Decoded = decode(Bytes, Types, []),
    to_record(loginresp, Decoded);
decode(userinfo, Bytes) ->
    Types = [{9, sticks, conversationinfo,
	      [is_record, repeated]},
	     {8, mutenotofications, conversationinfo,
	      [is_record, repeated]},
	     {7, latelyemotions, string, []},
	     {6, emotions, string, []},
	     {5, follows, conversationinfo, [is_record, repeated]},
	     {4, blacks, string, []}, {3, pushcontent, int32, []},
	     {2, mutenotofication, int32, []}, {1, uid, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(userinfo, Decoded);
decode(conversationinfo, Bytes) ->
    Types = [{5, updatetime, string, []},
	     {4, status, int32, []}, {3, members, string, []},
	     {2, name, string, []}, {1, cid, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(conversationinfo, Decoded);
decode(conversationmessage, Bytes) ->
    Types = [{2, msg, msg, [is_record, repeated]},
	     {1, totalnumber, int32, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(conversationmessage, Decoded);
decode(msg, Bytes) ->
    Types = [{9, msgstatus, string, []},
	     {8, msgtime, string, []}, {7, content, string, []},
	     {6, members, string, []}, {5, fromuser, string, []},
	     {4, msgtype, string, []},
	     {3, conversationinfo, conversationinfo, [is_record]},
	     {2, conversationid, string, []}, {1, mid, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(msg, Decoded);
decode(messageack, Bytes) ->
    Types = [{5, extra, string, []},
	     {4, msgtime, string, []}, {3, sid, string, []},
	     {2, conversationinfo, conversationinfo, [is_record]},
	     {1, mid, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(messageack, Decoded);
decode(pushcontent, Bytes) ->
    Types = [{2, extra, string, []},
	     {1, alert, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(pushcontent, Decoded);
decode(kick, Bytes) ->
    Types = [{1, time, string, []}],
    Decoded = decode(Bytes, Types, []),
    to_record(kick, Decoded).

decode(<<>>, _, Acc) -> Acc;
decode(<<Bytes/binary>>, Types, Acc) ->
    {{FNum, WireType}, Rest} =
	protobuffs:read_field_num_and_wire_type(Bytes),
    case lists:keysearch(FNum, 1, Types) of
      {value, {FNum, Name, Type, Opts}} ->
	  {Value1, Rest1} = case lists:member(is_record, Opts) of
			      true ->
				  {V, R} = protobuffs:decode_value(WireType,
								   bytes, Rest),
				  RecVal = decode(Type, V),
				  {RecVal, R};
			      false ->
				  {V, R} = protobuffs:decode_value(WireType,
								   Type, Rest),
				  {unpack_value(V, Type), R}
			    end,
	  case lists:member(repeated, Opts) of
	    true ->
		case lists:keytake(FNum, 1, Acc) of
		  {value, {FNum, Name, List}, Acc1} ->
		      decode(Rest1, Types,
			     [{FNum, Name,
			       lists:reverse([Value1 | lists:reverse(List)])}
			      | Acc1]);
		  false ->
		      decode(Rest1, Types, [{FNum, Name, [Value1]} | Acc])
		end;
	    false ->
		decode(Rest1, Types, [{FNum, Name, Value1} | Acc])
	  end;
      false -> exit({error, {unexpected_field_index, FNum}})
    end.

unpack_value(<<Binary/binary>>, string) ->
    binary_to_list(Binary);
unpack_value(Value, _) -> Value.

to_record(handshake, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, handshake), Record,
					 Name, Val)
		end,
		#handshake{}, DecodedTuples);
to_record(handshakeresp, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, handshakeresp),
					 Record, Name, Val)
		end,
		#handshakeresp{}, DecodedTuples);
to_record(bound, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, bound), Record,
					 Name, Val)
		end,
		#bound{}, DecodedTuples);
to_record(error, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, error), Record,
					 Name, Val)
		end,
		#error{}, DecodedTuples);
to_record(login, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, login), Record,
					 Name, Val)
		end,
		#login{}, DecodedTuples);
to_record(loginresp, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, loginresp), Record,
					 Name, Val)
		end,
		#loginresp{}, DecodedTuples);
to_record(userinfo, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, userinfo), Record,
					 Name, Val)
		end,
		#userinfo{}, DecodedTuples);
to_record(conversationinfo, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, conversationinfo),
					 Record, Name, Val)
		end,
		#conversationinfo{}, DecodedTuples);
to_record(conversationmessage, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields,
						     conversationmessage),
					 Record, Name, Val)
		end,
		#conversationmessage{}, DecodedTuples);
to_record(msg, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, msg), Record, Name,
					 Val)
		end,
		#msg{}, DecodedTuples);
to_record(messageack, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, messageack),
					 Record, Name, Val)
		end,
		#messageack{}, DecodedTuples);
to_record(pushcontent, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, pushcontent),
					 Record, Name, Val)
		end,
		#pushcontent{}, DecodedTuples);
to_record(kick, DecodedTuples) ->
    lists:foldl(fun ({_FNum, Name, Val}, Record) ->
			set_record_field(record_info(fields, kick), Record,
					 Name, Val)
		end,
		#kick{}, DecodedTuples).

set_record_field(Fields, Record, Field, Value) ->
    Index = list_index(Field, Fields),
    erlang:setelement(Index + 1, Record, Value).

list_index(Target, List) -> list_index(Target, List, 1).

list_index(Target, [Target | _], Index) -> Index;
list_index(Target, [_ | Tail], Index) ->
    list_index(Target, Tail, Index + 1);
list_index(_, [], _) -> 0.

